﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using ScrumTable.Common;
using ScrumTable.Common.Security;
using ScrumTable.Common.Threading;
using ScrumTable.DL.Data.Common;
using ScrumTable.DL.Data.Generic.Cache;
using ScrumTable.DL.Data.Generic.Properties;
using ScrumTable.DL.Data.Generic.Services;

#endregion

namespace ScrumTable.DL.Data.Generic
{
    /// <summary>
    /// Specifies the data connection for a data sync enabled storage.
    /// This class represents the global connection state and should determine
    /// the connection of each sub service.
    /// </summary>
    public abstract class DataConnectionBase : DisposeBase, IDataConnection
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        private readonly GenericDataContextBase _context;
        private CacheContext _cacheContext;
        private ConnectionState _state = ConnectionState.Disconnected;
        private ConnectionInformationManager _connectionManager;
        private IDataSaveManager _saveManager;

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        /// <summary>
        /// Returns true if the external system is connected and can be used.
        /// </summary>
        public bool IsConnected
        {
            get { return (_state == ConnectionState.Connected && _cacheContext != null); }
        }

        /// <summary>
        /// Gets the current connection state.
        /// </summary>
        /// <value></value>
        public ConnectionState ConnectionState
        {
            get
            {
                if (IsConnected)
                {
                    return ConnectionState.Connected;
                }
                return _state;
            }
        }

        /// <summary>
        /// Gets the underlying connection information which are needed for
        /// authenticating the user on the external systems.
        /// </summary>
        public ConnectionInformationManager ConnectionManager
        {
            get { return _connectionManager; }
        }

        /// <summary>
        /// Gets the hint to the connection location.
        /// </summary>
        public string ConnectionMoniker { get; private set; }

        /// <summary>
        /// Gets the work item store, stored in the Team Foundation Server.
        /// </summary>
        internal CacheContext DataCache
        {
            get { return _cacheContext; }
            set
            {
                if (_cacheContext != null)
                    _cacheContext.Dispose();

                if (_saveManager != null)
                    _saveManager.Dispose();

                if (value != null)
                {
                    _cacheContext = value;
                    _saveManager = _context.DataSaveManagerFactory.Create(_context);
                    _state = ConnectionState.Connected;
                    _context.StorageInternal.OnConnected();
                }
                else
                {
                    _saveManager = null;
                    _state = ConnectionState.Disconnected;
                    _context.StorageInternal.OnDisconnected();
                    _cacheContext = null;
                }
            }
        }

        /// <summary>
        /// Gets the save manager of the current data cache.
        /// </summary>
        internal IDataSaveManager SaveManager
        {
            get { return _saveManager; }
        }

        /// <summary>
        /// Gets the underlying data context instance.
        /// </summary>
        protected GenericDataContextBase DataContext
        {
            get { return _context; }
        }

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Creates a new DataConnection instance, no public constructor
        /// needed.
        /// </summary>
        protected DataConnectionBase(GenericDataContextBase context)
        {
            PreCondition.AssertNotNull(context, "context");

            _context = context;
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Connects to the server instance. Make sure you disconnect from the
        /// server after you finished your work with the data connection.
        /// </summary>
        /// <param name="connectionCtx">Specifies the connection context which provides information about the end system and the user.</param>
        /// <param name="callback">Specifies the callback used to retrieve the connection state. The callback value is of type DataResult.</param>
        /// <param name="dataMoniker">Specifies a hint (moniker) to the data location. This must be supported by the underlying driver.</param>
        public void Connect(ConnectionInformationManager connectionCtx, AsyncCallback callback, string dataMoniker)
        {
            EnsureNotDisposed();

            PreCondition.AssertNotNull(connectionCtx, "connectionCtx");
            PreCondition.AssertNotNull(callback, "callback");

            if (IsConnected) { Disconnect(); }
            if (!TryInitializeMoniker(callback, dataMoniker)) { return; }

            _connectionManager = connectionCtx;
            _context.DataLoader.LoadConnectionData(
                _context,
                result =>
                    _context.DataSyncDispatcher.BeginInvoke(
                        () =>
                            {
                                if (!((DataResult<CacheContext>) result).HasError)
                                {
                                    DataCache = ((DataResult<CacheContext>) result).Data;
                                    callback(new DataResult());
                                }
                                else
                                {
                                    callback(new DataResult(((DataResult<CacheContext>) result).Error));
                                }
                            }));
        }

        /// <summary>
        /// Changes the user and stores its changes to the back end system.
        /// </summary>
        /// <param name="credentialsObject">Specifies the login credential information object.</param>
        /// <param name="callbackEvent">Specifies the callback which is fired after the
        /// process has been completed. The callback is of type ICancellableDataResult
        /// and its Data property contains true, if all changes have been accepted.</param>
        public void ChangeUser(IDataObject credentialsObject, AsyncCallback callbackEvent)
        {
            EnsureNotDisposed();

            PreCondition.AssertNotNull(credentialsObject, "credentialsObject");
            PreCondition.AssertNotNull(callbackEvent, "callbackEvent");
            PreCondition.AssertTrue(credentialsObject is MemberDataObject, Resources.ExcDataConChangeUserInvalidCredObj);

            if (!IsConnected)
                throw new InvalidOperationException(Resources.ExcDatConAlreadyDisconnected);

            object backEndId = credentialsObject.BackEndId;

            if (backEndId == null)
                throw new InvalidOperationException(Resources.ExcDataConMemberMustBeAbleToLogIn);

            SaveManager.Stop(
                rawResult =>
                    {
                        ICancellableDataResult result = (ICancellableDataResult) rawResult;
                        try
                        {
                            DefaultCancellableDataResult memberChangeResult = new DefaultCancellableDataResult(result.DataAccepted);
                            callbackEvent(memberChangeResult);

                            if (result.DataAccepted && !memberChangeResult.Canceled)
                            {
                                ConnectionManager.Select((string) backEndId);
                            }
                        }
                        finally
                        {
                            result.Cancel();
                        }
                    });
        }

        /// <summary>
        /// Changes the user after the latests changes have been stored to the back end system.
        /// </summary>
        /// <param name="credentialsObject">Specifies the login credential information object.</param>
        public void ChangeUser(IDataObject credentialsObject)
        {
            EnsureNotDisposed();

            PreCondition.AssertNotNull(credentialsObject, "credentialsObject");
            PreCondition.AssertTrue(credentialsObject is MemberDataObject, Resources.ExcDataConChangeUserInvalidCredObj);

            if (!IsConnected)
                throw new InvalidOperationException(Resources.ExcDatConAlreadyDisconnected);

            object backEndId = credentialsObject.BackEndId;

            if (backEndId == null)
                throw new InvalidOperationException(Resources.ExcDataConMemberMustBeAbleToLogIn);

            SaveManager.Stop(
                rawResult =>
                {
                    ICancellableDataResult result = (ICancellableDataResult)rawResult;
                    try { ConnectionManager.Select((string)backEndId); }
                    finally { result.Cancel(); }
                });
        }

        /// <summary>
        /// Disconnects from a connected server instance and clears all
        /// data in the current data context. After a disconnect, you can
        /// reconnect using the connect method.
        /// </summary>
        public void Disconnect()
        {
            EnsureNotDisposed();

            if (!IsConnected)
                throw new InvalidOperationException(Resources.ExcDatConAlreadyDisconnected);

            DataCache = null;
        }

        /// <summary>
        /// Releases managed (c# or .net) resources.
        /// </summary>
        protected override void ReleaseManagedResources()
        {
            DataCache = null;
        }

        /// <summary>
        /// Returns the default moniker if the provided moniker contains an invalid value.
        /// </summary>
        /// <param name="moniker">Specifies the moniker to validate.</param>
        /// <returns>Returns the default moniker for the current driver instance.</returns>
        protected virtual string ValidateMoniker(string moniker)
        {
            return moniker;
        }

        private bool TryInitializeMoniker(AsyncCallback callback, string dataMoniker)
        {
            try
            {
                ConnectionMoniker = ValidateMoniker(dataMoniker);
                return true;
            }
            catch (Exception e)
            {
                callback(new DataResult(e));
                return false;
            }
        }

        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        #endregion
    }
}